﻿using CatClient.Message.Internals;
using CatClient.Util;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

namespace CatClient.Message.Spi.Codec
{
    public class PlainTextMessageCodec : IMessageCodec
    {
        public enum Policy
        {
            DEFAULT,
            WITHOUT_STATUS,
            WITH_DURATION
        }

        protected internal class BufferHelper
        {
            private readonly UTF8Encoding _mEncoding = new UTF8Encoding();

            public string Read(ChannelBuffer buf, byte separator)
            {
                int num = buf.BytesBefore(separator);
                if (num < 0)
                {
                    return null;
                }
                byte[] array = new byte[num];
                buf.ReadBytes(array);
                buf.ReadByte();
                return Encoding.UTF8.GetString(array);
            }

            public string ReadRaw(ChannelBuffer buf, byte separator)
            {
                int num = buf.BytesBefore(separator);
                if (num < 0)
                {
                    return null;
                }
                byte[] array = new byte[num];
                buf.ReadBytes(array);
                buf.ReadByte();
                int num2 = array.Length;
                for (int i = 0; i < num2; i++)
                {
                    if (array[i] == 92 && i + 1 < num2)
                    {
                        byte b = array[i + 1];
                        if (b == 116)
                        {
                            array[i] = 9;
                        }
                        else if (b == 114)
                        {
                            array[i] = 13;
                        }
                        else if (b == 110)
                        {
                            array[i] = 10;
                        }
                        else
                        {
                            array[i] = b;
                        }
                        Array.Copy(array, i + 2, array, i + 1, num2 - i - 2);
                        num2--;
                    }
                }
                return Encoding.UTF8.GetString(array, 0, num2);
            }

            public int Write(ChannelBuffer buf, byte b)
            {
                buf.WriteByte(b);
                return 1;
            }

            public int Write(ChannelBuffer buf, string str)
            {
                if (str == null)
                {
                    str = "null";
                }
                byte[] bytes = this._mEncoding.GetBytes(str);
                buf.WriteBytes(bytes);
                return bytes.Length;
            }

            public int WriteRaw(ChannelBuffer buf, string str)
            {
                if (str == null)
                {
                    str = "null";
                }
                byte[] bytes = this._mEncoding.GetBytes(str);
                int num = bytes.Length;
                int num2 = num;
                int num3 = 0;
                for (int i = 0; i < num; i++)
                {
                    byte b = bytes[i];
                    if (b == 9 || b == 13 || b == 10 || b == 92)
                    {
                        buf.WriteBytes(bytes, num3, i - num3);
                        buf.WriteByte('\\');
                        if (b == 9)
                        {
                            buf.WriteByte('t');
                        }
                        else if (b == 13)
                        {
                            buf.WriteByte('r');
                        }
                        else if (b == 10)
                        {
                            buf.WriteByte('n');
                        }
                        else
                        {
                            buf.WriteByte(b);
                        }
                        num2++;
                        num3 = i + 1;
                    }
                }
                if (num > num3)
                {
                    buf.WriteBytes(bytes, num3, num - num3);
                }
                return num2;
            }
        }

        protected internal class DateHelper
        {
            public string Format(long timestamp)
            {
                return new DateTime(timestamp * 10000L).ToString("yyyy-MM-dd HH:mm:ss.fff");
            }

            public long Parse(string str)
            {
                return DateTime.ParseExact(str, "yyyy-MM-dd HH:mm:ss.fff", CultureInfo.CurrentCulture).Ticks / 10000L;
            }
        }

        private const string ID = "PT1";

        private const byte TAB = 9;

        private const byte LF = 10;

        private readonly PlainTextMessageCodec.BufferHelper _mBufferHelper;

        private readonly PlainTextMessageCodec.DateHelper _mDateHelper;

        public PlainTextMessageCodec()
        {
            this._mBufferHelper = new PlainTextMessageCodec.BufferHelper();
            this._mDateHelper = new PlainTextMessageCodec.DateHelper();
        }

        public virtual IMessageTree Decode(ChannelBuffer buf)
        {
            DefaultMessageTree defaultMessageTree = new DefaultMessageTree();
            this.Decode(buf, defaultMessageTree);
            return defaultMessageTree;
        }

        public virtual void Decode(ChannelBuffer buf, IMessageTree tree)
        {
            this.DecodeHeader(buf, tree);
            if (buf.ReadableBytes() > 0)
            {
                this.DecodeMessage(buf, tree);
            }
        }

        public virtual void Encode(IMessageTree tree, ChannelBuffer buf)
        {
            int num = 0;
            buf.WriteInt(0);
            num += this.EncodeHeader(tree, buf);
            if (tree.Message != null)
            {
                num += this.EncodeMessage(tree.Message, buf);
            }
            buf.SetInt(0, num);
        }

        protected internal void DecodeHeader(ChannelBuffer buf, IMessageTree tree)
        {
            PlainTextMessageCodec.BufferHelper expr_06 = this._mBufferHelper;
            string text = expr_06.Read(buf, 9);
            string domain = expr_06.Read(buf, 9);
            string hostName = expr_06.Read(buf, 9);
            string ipAddress = expr_06.Read(buf, 9);
            string threadGroupName = expr_06.Read(buf, 9);
            string threadId = expr_06.Read(buf, 9);
            string threadName = expr_06.Read(buf, 9);
            string messageId = expr_06.Read(buf, 9);
            string parentMessageId = expr_06.Read(buf, 9);
            string rootMessageId = expr_06.Read(buf, 9);
            string sessionToken = expr_06.Read(buf, 10);
            if ("PT1".Equals(text))
            {
                tree.Domain = domain;
                tree.HostName = hostName;
                tree.IpAddress = ipAddress;
                tree.ThreadGroupName = threadGroupName;
                tree.ThreadId = threadId;
                tree.ThreadName = threadName;
                tree.MessageId = messageId;
                tree.ParentMessageId = parentMessageId;
                tree.RootMessageId = rootMessageId;
                tree.SessionToken = sessionToken;
                return;
            }
            throw new Exception("Unrecognized id(" + text + ") for plain text message codec!");
        }

        protected internal IMessage DecodeLine(ChannelBuffer buf, ITransaction parent, Stack<ITransaction> stack, IMessageTree tree)
        {
            PlainTextMessageCodec.BufferHelper mBufferHelper = this._mBufferHelper;
            char c = (char)buf.ReadByte();
            string str = mBufferHelper.Read(buf, 9);
            string type = mBufferHelper.Read(buf, 9);
            string name = mBufferHelper.Read(buf, 9);
            if (c <= 'H')
            {
                if (c != 'A')
                {
                    if (c != 'E')
                    {
                        if (c == 'H')
                        {
                            DefaultHeartbeat defaultHeartbeat = new DefaultHeartbeat(type, name, null);
                            string status = mBufferHelper.Read(buf, 9);
                            string keyValuePairs = mBufferHelper.ReadRaw(buf, 9);
                            mBufferHelper.Read(buf, 10);
                            defaultHeartbeat.Timestamp = this._mDateHelper.Parse(str);
                            defaultHeartbeat.Status = status;
                            defaultHeartbeat.AddData(keyValuePairs);
                            if (parent != null)
                            {
                                parent.AddChild(defaultHeartbeat);
                                return parent;
                            }
                            return defaultHeartbeat;
                        }
                    }
                    else
                    {
                        DefaultEvent defaultEvent = new DefaultEvent(type, name, null);
                        string status2 = mBufferHelper.Read(buf, 9);
                        string keyValuePairs2 = mBufferHelper.ReadRaw(buf, 9);
                        mBufferHelper.Read(buf, 10);
                        defaultEvent.Timestamp = this._mDateHelper.Parse(str);
                        defaultEvent.Status = status2;
                        defaultEvent.AddData(keyValuePairs2);
                        if (parent != null)
                        {
                            parent.AddChild(defaultEvent);
                            return parent;
                        }
                        return defaultEvent;
                    }
                }
                else
                {
                    DefaultTransaction defaultTransaction = new DefaultTransaction(type, name, null);
                    string status3 = mBufferHelper.Read(buf, 9);
                    string text = mBufferHelper.Read(buf, 9);
                    string keyValuePairs3 = mBufferHelper.ReadRaw(buf, 9);
                    mBufferHelper.Read(buf, 10);
                    defaultTransaction.Timestamp = this._mDateHelper.Parse(str);
                    defaultTransaction.Status = status3;
                    defaultTransaction.AddData(keyValuePairs3);
                    long durationInMicros = long.Parse(text.Substring(0, text.Length - 2), NumberStyles.Integer);
                    defaultTransaction.DurationInMicros = durationInMicros;
                    if (parent != null)
                    {
                        parent.AddChild(defaultTransaction);
                        return parent;
                    }
                    return defaultTransaction;
                }
            }
            else if (c <= 'M')
            {
                if (c != 'L')
                {
                    if (c == 'M')
                    {
                        DefaultMetric defaultMetric = new DefaultMetric(type, name, null);
                        string status4 = mBufferHelper.Read(buf, 9);
                        string keyValuePairs4 = mBufferHelper.ReadRaw(buf, 9);
                        mBufferHelper.Read(buf, 10);
                        defaultMetric.Timestamp = this._mDateHelper.Parse(str);
                        defaultMetric.Status = status4;
                        defaultMetric.AddData(keyValuePairs4);
                        if (parent != null)
                        {
                            parent.AddChild(defaultMetric);
                            return parent;
                        }
                        return defaultMetric;
                    }
                }
                else
                {
                    DefaultTrace defaultTrace = new DefaultTrace(type, name, null);
                    string status5 = mBufferHelper.Read(buf, 9);
                    string keyValuePairs5 = mBufferHelper.Read(buf, 9);
                    mBufferHelper.Read(buf, 10);
                    defaultTrace.Timestamp = this._mDateHelper.Parse(str);
                    defaultTrace.Status = status5;
                    defaultTrace.AddData(keyValuePairs5);
                    if (parent != null)
                    {
                        parent.AddChild(defaultTrace);
                        return parent;
                    }
                    return defaultTrace;
                }
            }
            else
            {
                if (c == 'T')
                {
                    string status6 = mBufferHelper.Read(buf, 9);
                    string text2 = mBufferHelper.Read(buf, 9);
                    string keyValuePairs6 = mBufferHelper.ReadRaw(buf, 9);
                    mBufferHelper.Read(buf, 10);
                    parent.Status = status6;
                    parent.AddData(keyValuePairs6);
                    long durationInMicros2 = long.Parse(text2.Substring(0, text2.Length - 2), NumberStyles.Integer);
                    parent.DurationInMicros = durationInMicros2;
                    return stack.Pop();
                }
                if (c == 't')
                {
                    IMessage message = new DefaultTransaction(type, name, null);
                    mBufferHelper.Read(buf, 10);
                    message.Timestamp = this._mDateHelper.Parse(str);
                    if (parent != null)
                    {
                        parent.AddChild(message);
                    }
                    stack.Push(parent);
                    return message;
                }
            }
            LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Error(string.Concat(new object[]
            {
                "Unknown identifier(",
                c.ToString(),
                ") of message: ",
                buf
            }));
            return parent;
        }

        protected internal void DecodeMessage(ChannelBuffer buf, IMessageTree tree)
        {
            Stack<ITransaction> stack = new Stack<ITransaction>();
            IMessage message = this.DecodeLine(buf, null, stack, tree);
            tree.Message = message;
            while (buf.ReadableBytes() > 0)
            {
                IMessage message2 = this.DecodeLine(buf, (ITransaction)message, stack, tree);
                if (!(message2 is ITransaction))
                {
                    break;
                }
                message = message2;
            }
        }

        protected internal int EncodeHeader(IMessageTree tree, ChannelBuffer buf)
        {
            PlainTextMessageCodec.BufferHelper mBufferHelper = this._mBufferHelper;
            return 0 + mBufferHelper.Write(buf, "PT1") + mBufferHelper.Write(buf, 9) + mBufferHelper.Write(buf, tree.Domain) + mBufferHelper.Write(buf, 9) + mBufferHelper.Write(buf, tree.HostName) + mBufferHelper.Write(buf, 9) + mBufferHelper.Write(buf, tree.IpAddress) + mBufferHelper.Write(buf, 9) + mBufferHelper.Write(buf, tree.ThreadGroupName) + mBufferHelper.Write(buf, 9) + mBufferHelper.Write(buf, tree.ThreadId) + mBufferHelper.Write(buf, 9) + mBufferHelper.Write(buf, tree.ThreadName) + mBufferHelper.Write(buf, 9) + mBufferHelper.Write(buf, tree.MessageId) + mBufferHelper.Write(buf, 9) + mBufferHelper.Write(buf, tree.ParentMessageId) + mBufferHelper.Write(buf, 9) + mBufferHelper.Write(buf, tree.RootMessageId) + mBufferHelper.Write(buf, 9) + mBufferHelper.Write(buf, tree.SessionToken) + mBufferHelper.Write(buf, 10);
        }

        protected internal int EncodeLine(IMessage message, ChannelBuffer buf, char type, PlainTextMessageCodec.Policy policy)
        {
            PlainTextMessageCodec.BufferHelper mBufferHelper = this._mBufferHelper;
            int num = 0;
            num += mBufferHelper.Write(buf, (byte)type);
            if (type == 'T' && message is ITransaction)
            {
                long durationInMillis = ((ITransaction)message).DurationInMillis;
                num += mBufferHelper.Write(buf, this._mDateHelper.Format(message.Timestamp + durationInMillis));
            }
            else
            {
                num += mBufferHelper.Write(buf, this._mDateHelper.Format(message.Timestamp));
            }
            num += mBufferHelper.Write(buf, 9);
            num += mBufferHelper.Write(buf, message.Type);
            num += mBufferHelper.Write(buf, 9);
            num += mBufferHelper.Write(buf, message.Name);
            num += mBufferHelper.Write(buf, 9);
            if (policy != PlainTextMessageCodec.Policy.WITHOUT_STATUS)
            {
                num += mBufferHelper.Write(buf, message.Status);
                num += mBufferHelper.Write(buf, 9);
                object data = message.Data;
                if (policy == PlainTextMessageCodec.Policy.WITH_DURATION && message is ITransaction)
                {
                    num += mBufferHelper.Write(buf, ((ITransaction)message).DurationInMicros.ToString(CultureInfo.InvariantCulture));
                    num += mBufferHelper.Write(buf, "us");
                    num += mBufferHelper.Write(buf, 9);
                }
                num += mBufferHelper.WriteRaw(buf, data.ToString());
                num += mBufferHelper.Write(buf, 9);
            }
            return num + mBufferHelper.Write(buf, 10);
        }

        public int EncodeMessage(IMessage message, ChannelBuffer buf)
        {
            if (message is ITransaction)
            {
                ITransaction transaction = message as ITransaction;
                IList<IMessage> children = transaction.Children;
                if (children.Count == 0)
                {
                    return this.EncodeLine(transaction, buf, 'A', PlainTextMessageCodec.Policy.WITH_DURATION);
                }
                int num = 0;
                int count = children.Count;
                num += this.EncodeLine(transaction, buf, 't', PlainTextMessageCodec.Policy.WITHOUT_STATUS);
                for (int i = 0; i < count; i++)
                {
                    IMessage message2 = children[i];
                    num += this.EncodeMessage(message2, buf);
                }
                return num + this.EncodeLine(transaction, buf, 'T', PlainTextMessageCodec.Policy.WITH_DURATION);
            }
            else
            {
                if (message is IEvent)
                {
                    return this.EncodeLine(message, buf, 'E', PlainTextMessageCodec.Policy.DEFAULT);
                }
                if (message is ITrace)
                {
                    return this.EncodeLine(message, buf, 'L', PlainTextMessageCodec.Policy.DEFAULT);
                }
                if (message is IHeartbeat)
                {
                    return this.EncodeLine(message, buf, 'H', PlainTextMessageCodec.Policy.DEFAULT);
                }
                if (message is IMetric)
                {
                    return this.EncodeLine(message, buf, 'M', PlainTextMessageCodec.Policy.DEFAULT);
                }
                throw new Exception(string.Format("Unsupported message type: {0}.", message.Type));
            }
        }
    }
}
